using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using Myotragus.Data.Criteria;
using Myotragus.Data.Tests.Domain;
using Myotragus.Data.Util;
using NHibernate;
using NHibernate.Criterion;
using NUnit.Framework;

namespace Myotragus.Data.Tests.Tests.Criteria
{
	[TestFixture]
	public class ReadQueryBuilderFixtures
	{
		[Test]
		public void FindById()
		{
			var session = new Mock<ISession>();
			var factory = new Mock<ISessionFactoryAdapter>();
			var type = typeof(ITestEntity);
			const int id1 = 1;
			const int id2 = 2;
			var obj = new object();

			factory.Setup(m => m.OpenSession()).Returns(session.Object);
			session.Setup(m => m.Get(type, id1)).Returns(obj);
			session.Setup(m => m.Get(type, id2)).Returns(null);

			var builder = new ReadQueryBuilder(factory.Object);

			Assert.AreEqual(obj, builder.FindById(type, id1));
			Assert.IsNull(builder.FindById(type, id2));

			Assert.IsTrue(builder.Exists(type, id1));
			Assert.IsFalse(builder.Exists(type, id2));

			factory.VerifyAll();
			session.VerifyAll();
		}

		[Test]
		public void CreateMatchingCriteria()
		{
			var session = new Mock<ISession>();
			var factory = new Mock<ISessionFactoryAdapter>();
			var type = typeof(ITestEntity);
			var criteria = new Mock<ICriteria>();
			var pattern = new { T1 = 1, T2 = 2 };

			factory.Setup(m => m.OpenSession()).Returns(session.Object);
			session.Setup(m => m.CreateCriteria(typeof(ITestEntity))).Returns(criteria.Object);

			foreach (var prop in ReflectionHelper.GetProperties(pattern))
			{
				var info = prop;
				criteria.Setup(m =>
					m.Add(It.Is<ICriterion>(criterion =>
						criterion.Equals(
							Property.ForName(info.Name).Eq(info.GetValue(pattern, new object[0])))
						)));
			}

			var builder = new ReadQueryBuilder(factory.Object);

			builder.CreateMatchingCriteria(type, pattern);

			factory.VerifyAll();
		}

		[Test]
		public void CreateLikeCriteria()
		{
			var session = new Mock<ISession>();
			var factory = new Mock<ISessionFactoryAdapter>();
			var type = typeof(ITestEntity);
			var criteria = new Mock<ICriteria>();
			var pattern = new { T1 = 1, T2 = 2, T3 = "Erich" };

			factory.Setup(m => m.OpenSession()).Returns(session.Object);
			session.Setup(m => m.CreateCriteria(typeof(ITestEntity))).Returns(criteria.Object);

			var comparison = ReflectionHelper.GetProperties(pattern)
				.Select(p => GetCriterion(p, pattern)).ToList();

			criteria.Setup(m => m.Add(It.Is<ICriterion>(
				c => Contains(c, comparison))));
			criteria.Setup(m => m.Add(It.Is<ICriterion>(
				c => !Contains(c, comparison)))).Throws<Exception>();

			var builder = new ReadQueryBuilder(factory.Object);

			builder.CreateLikeCriteria(type, pattern);

			factory.VerifyAll();
		}

		private static bool Contains(ICriterion c, IEnumerable<ICriterion> cc)
		{
			var result = cc.Where(x => x.ToString() == c.ToString()).Any();
			return result;
		}

		private static ICriterion GetCriterion(PropertyInfo prop, object pattern)
		{
			var value = prop.GetValue(pattern, ReflectionHelper.EmptyObjects);
			var result = prop.PropertyType == ReflectionHelper.TypeOfString
					? Property.ForName(prop.Name).Like(value.ToString(), MatchMode.Anywhere)
					: Property.ForName(prop.Name).Eq(value);

			return result;
		}
	}
}